package com.gusto.engine.semsim.measures.impl;

import java.util.ArrayList;
import java.util.List;

import org.apache.log4j.Logger;

import com.gusto.engine.beans.model.Res;
import com.gusto.engine.semsim.exceptions.SimilarityException;
import com.gusto.engine.semsim.measures.SetSimilarity;
import com.gusto.engine.semsim.measures.types.SemTypes;

/**
 * <p>Jaccard distance on binary data.  
 * The 2 sets are represented as a vector of binary values 
 * on the complete set of elements<br/>
 * Sim = [common item] / ([items in set1 &amp; not in common] + [items in set2 &amp; not in common] + [common items])<br/>
 * Sim = [common item] / ([items in set1] + [items in set2] - [common items])<br/>
 * The similarity measure is between [0, 1],
 * 0 means there no common elements, 1 means a perfect match between sets</p>
 * 
 * @author amokrane.belloui@gmail.com
 */
public class JaccardBinarySetSimilarity implements SetSimilarity {
	
	private Logger log = Logger.getLogger(getClass());
	
	private List<? extends Object> stopwords = new ArrayList<Object>();
	public void setStopwords(List<? extends Object> stopwords) {
		this.stopwords = stopwords;
		log.debug(stopwords);
	}
	
	private SemTypes type;
	
	public JaccardBinarySetSimilarity(SemTypes type) {
		this.type = type;
	}
	
	public String getId() {
		String id = "[" + getClass().getName() + "|" + type + "|" + stopwords + "]";
		return id;
	}
	
	public Double getSimilarity(List<? extends Object> list1, List<? extends Object> list2) throws SimilarityException {
		int common = 0;
		
		if (list1 == null) { list1 = new ArrayList<Object>(); }
		if (list2 == null) { list2 = new ArrayList<Object>(); }
		
		list1.removeAll(stopwords);
		list2.removeAll(stopwords);
		
		for (Object value1 : list1) {
			for (Object value2 : list2) {
				if (type.equals(SemTypes.VALUE)) {
					if (value1.equals(value2)) {
						if (!stopwords.contains(value1)) {
							common ++;
							log.debug(value1 + " " + value2 + " " + value1.equals(value2));
						}
						else {
							log.debug("Stop word" + value1 + " ");
						}
					}
				}
				else if (type.equals(SemTypes.RESOURCE)) {
					Res r1 = (Res)value1;
					Res r2 = (Res)value2;
					if (r1.equals(r2)) {
						if (!stopwords.contains(r1)) {
							common ++;
							log.debug(r1 + " " + r2 + " " + r1.equals(r2));
						}
						else {
							log.debug("Stop word" + r1 + " ");
						}
					}
				}
			}
		}
		
		// common / (from1 + from2 - common)
		// Normalized between [0, 1]
		log.debug("Common = " + common + " / (" + list1.size() + " + " + list2.size() + " - " + common + ")");
		Double sim = new Double(common) / (list1.size() + list2.size() - new Double(common));
		log.info("Similarity = '" + sim + "' (common," + common + ")");
		
		if (sim.isInfinite() || sim.isNaN()) {
			return 0.0;
		}
		return sim;
	}

	public SemTypes getType() {
		return this.type;
	}

}
